//-----------------------------------------------------------------------
// <copyright file="CompositeTypeTest.cs" company="NMock2">
//
//   http://www.sourceforge.net/projects/NMock2
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// </copyright>
// This is the easiest way to ignore StyleCop rules on this file, even if we shouldn't use this tag:
// <auto-generated />
//-----------------------------------------------------------------------
namespace NMock2.Internal.Test
{
    using NMock2.Matchers;
    using NUnit.Framework;
    using System;
    using System.Collections;
    using System.Reflection;
    using System.Collections.Generic;

    [TestFixture]
    public class CompositeTypeTest
    {
        private readonly string IEnumerableName = typeof(IEnumerable).Name;
        private readonly string IDisposableName = typeof(IDisposable).Name;
        private readonly string IEqualityComparerName = typeof(IEqualityComparer).Name;
        private readonly string SomeClassName = typeof(SomeClass).Name;
        
        public abstract class SomeClass
        {

        }

        public abstract class SomeOtherClass
        {

        }

        
        [Test]
        public void AllConstructorsProduceEquivalentInstances()
        {
            Assert.AreEqual(
                new CompositeType(new[] { typeof(SomeClass), typeof(IEnumerable), typeof(IDisposable) }),
                new CompositeType(typeof(SomeClass), new[] { typeof(IEnumerable), typeof(IDisposable) }));
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void ConstructorThrowsArgumentNullExceptionWhenNullTypeSupplied()
        {
            new CompositeType(null, new Type[0]);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void ConstructorThrowsArgumentExceptionWhenEmptyTypeArraySupplied()
        {
            new CompositeType(new Type[0]);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void ConstructorThrowsArgumentExceptionWhenMoreThanOneClassTypeSupplied()
        {
            new CompositeType(new Type[] { typeof(SomeClass), typeof(IList), typeof(SomeOtherClass) });
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void ConstructorThrowsArgumentExceptionWhenValueTypeSupplied()
        {
            new CompositeType(new Type[] { typeof(IList), typeof(int) });
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void ConstructorThrowsArgumentExceptionWhenDelegateTypeSupplied()
        {
            new CompositeType(new Type[] { typeof(IList), typeof(EventHandler) });
        }

        [Test]
        public void PrimaryTypeAndAdditionalInterfaceTypesCorrectWhenOnlyOneTypeSpecified()
        {
            CompositeType type = new CompositeType(new[] { typeof(IDisposable) });

            Assert.AreEqual(typeof(IDisposable), type.PrimaryType, "Incorrect PrimaryType");
            CollectionAssert.IsEmpty(type.AdditionalInterfaceTypes, "Incorrect AdditionalInterfaceTypes");
        }

        [Test]
        public void PrimaryTypeAndAdditionalInterfaceTypesIdentifiedAndOrderedCorrectly()
        {
            CompositeType type = new CompositeType(new[] { typeof(IDisposable), typeof(IList), typeof(IEnumerable) });

            Assert.AreEqual(typeof(IEnumerable), type.PrimaryType, "Incorrect PrimaryType");
            CollectionAssert.AreEqual(new[] { typeof(IList), typeof(IDisposable) }, type.AdditionalInterfaceTypes, "Incorrect AdditionalInterfaceTypes");
        }

        [Test]
        public void PrimaryTypeAndAdditionalInterfaceTypesIdentifiedAndOrderedCorrectlyWhenClassTypeSpecified()
        {
            CompositeType type = new CompositeType(new[] { typeof(IDisposable), typeof(SomeClass), typeof(IEnumerable) });

            Assert.AreEqual(typeof(SomeClass), type.PrimaryType, "Incorrect PrimaryType");
            CollectionAssert.AreEqual(new[] { typeof(IEnumerable), typeof(IDisposable) }, type.AdditionalInterfaceTypes, "Incorrect AdditionalInterfaceTypes");
        }

        [Test]
        public void ToStringListsTypesInExpectedOrder()
        {
            CompositeType type = new CompositeType(new Type[] {
                typeof(IEnumerable),
                typeof(IDisposable),
                typeof(SomeClass),
                typeof(IEqualityComparer)} );

            // Should be ordered by namespace-qualified name

            Assert.AreEqual(
                "{" +
                SomeClassName + ", " +
                IEnumerableName + ", " +
                IEqualityComparerName + ", " +
                IDisposableName + "}",
                type.ToString());
        }

        [Test]
        public void ToStringListsSingleTypeCorrectly()
        {
            CompositeType type = new CompositeType(new Type[] { typeof(IEnumerable) });

            Assert.AreEqual("{" + IEnumerableName + "}", type.ToString());
        }

        [Test]
        public void GetHashCodeAndEqualsAreConsistentForIdenticalTypes()
        {
            Type[] types = new[] { typeof(IEnumerable), typeof(IDisposable), typeof(SomeClass) };

            Assert.AreEqual(new CompositeType(types).GetHashCode(), new CompositeType(types).GetHashCode(), "Hash codes differ");
            Assert.AreEqual(new CompositeType(types), new CompositeType(types), "Expected to be equal");
        }

        [Test]
        public void GetHashCodeAndEqualsAreConsistentForSameTypesButDifferentOrder()
        {
            Type[] types = new[] { typeof(IEnumerable), typeof(IDisposable), typeof(SomeClass) };
            Type[] otherTypes = new[] { typeof(SomeClass), typeof(IEnumerable), typeof(IDisposable) };

            Assert.AreEqual(new CompositeType(types).GetHashCode(), new CompositeType(otherTypes).GetHashCode(), "Hash codes differ");
            Assert.AreEqual(new CompositeType(types), new CompositeType(otherTypes), "Expected to be equal");
        }

        [Test]
        public void GetHashCodeAndEqualsAreConsistentForNonMatchingTypes()
        {
            Type[] types = new[] { typeof(SomeClass), typeof(IEnumerable) };
            Type[] otherTypes = new[] { typeof(SomeClass), typeof(ICollection) };

            Assert.AreNotEqual(new CompositeType(types).GetHashCode(), new CompositeType(otherTypes).GetHashCode(), "Hash codes are the same");
            Assert.AreNotEqual(new CompositeType(types), new CompositeType(otherTypes), "Expected to not be equal");
        }

        [Test]
        public void EqualsReturnsFalseForNullInstance()
        {
            Assert.IsFalse(new CompositeType(new[] { typeof(IEnumerable) }).Equals((object)null)); // Exercises Equals(object) overload
            Assert.IsFalse(new CompositeType(new[] { typeof(IEnumerable) }).Equals(null));  // Exercises Equals(CompositeType) overload
        }

        [Test]
        public void EqualsReturnsFalseForDifferingTypeCounts()
        {
            Assert.IsFalse(
                new CompositeType(new[] { typeof(IEnumerable), typeof(IDisposable) })
                    .Equals( new CompositeType(new[] { typeof(IEnumerable), typeof(IDisposable), typeof(ICollection) })));
        }


        #region GetMatchingMethods() tests

        // TODO: Duplicate types test

        public interface IAdvancedCalculator : ICalculator
        {
            decimal Add(decimal a, decimal b);
            decimal Subtract(decimal a, decimal b);
        }

        public interface ICalculator
        {
            int Add(int a, int b);
            int Subtract(int a, int b);
        }

        public abstract class CalculatorImpl : ICalculator
        {
            public abstract int Add(int a, int b);
            public abstract int Subtract(int a, int b);

            protected void ProtectedMethod() { }
            internal void InternalMethod() { }
            protected internal void ProtectedInternalMethod() { }
            private void PrivateMethod() { }
        }

        public abstract class AlternateCalculatorImpl : CalculatorImpl
        {
            protected abstract string Display(object value);
        }


        [Test]
        public void GetMatchingMethodsCanLocateMethodOnClass()
        {
            MethodInfo expected = typeof(CalculatorImpl).GetMethod("Add");

            AssertMethodWasLocated(expected, new SameMatcher(expected), typeof(CalculatorImpl));
        }

        [Test]
        public void GetMatchingMethodsCanLocateMethodOnInterface()
        {
            MethodInfo expected = typeof(ICalculator).GetMethod("Add");

            AssertMethodWasLocated(expected, new SameMatcher(expected), typeof(ICalculator));
        }

        [Test]
        public void GetMatchingMethodsCanLocateMethodOnInheritedInterface()
        {
            MethodInfo expected = typeof(ICalculator).GetMethod("Add");

            AssertMethodWasLocated(expected, new SameMatcher(expected), typeof(IAdvancedCalculator));
        }

        [Test]
        public void GetMatchingMethodsCanLocateMethodOnSuperClass()
        {
            MethodInfo expected = typeof(AlternateCalculatorImpl).GetMethod("Add");

            AssertMethodWasLocated(expected, new SameMatcher(expected), typeof(AlternateCalculatorImpl));
        }

        [Test]
        public void GetMatchingMethodsCanReturnJustFirstMatch()
        {
            // For interfaces
            Assert.AreEqual(1, new CompositeType(new[] { typeof(IAdvancedCalculator) })
                                        .GetMatchingMethods(NMock2.Is.Anything, true).Count);

            // For classes
            Assert.AreEqual(1, new CompositeType(new[] { typeof(CalculatorImpl) })
                                        .GetMatchingMethods(NMock2.Is.Anything, true).Count);
        }

        [Test]
        public void GetMatchingMethodsCanLocateMultipleMethodsAcrossDifferentTypes()
        {
            MethodInfo[] expected = new[] { typeof(CalculatorImpl).GetMethod("Add"),
                                            typeof(IAdvancedCalculator).GetMethod("Add", new[] { typeof(decimal), typeof(decimal) }) };

            AssertAllMethodsWereLocated(
                expected,
                new MethodNameMatcher("Add"),
                typeof(CalculatorImpl), typeof(IAdvancedCalculator));
        }

        [Test]
        public void GetMatchingMethodsCanLocateProtectedMethods()
        {
            AssertMethodWasLocated("ProtectedMethod", new MethodNameMatcher("ProtectedMethod"), typeof(CalculatorImpl));
        }

        [Test]
        public void GetMatchingMethodsCanLocateInternalMethods()
        {
            AssertMethodWasLocated("InternalMethod", new MethodNameMatcher("InternalMethod"), typeof(CalculatorImpl));
        }

        [Test]
        public void GetMatchingMethodsCanLocateProtectedInternalMethods()
        {
            AssertMethodWasLocated("ProtectedInternalMethod", new MethodNameMatcher("ProtectedInternalMethod"), typeof(CalculatorImpl));
        }

        [Test]
        public void GetMatchingMethodsIgnoresPrivateMethods()
        {
            CollectionAssert.IsEmpty(new CompositeType(new[] { typeof(CalculatorImpl) })
                                        .GetMatchingMethods(new MethodNameMatcher("PrivateMethod"), true));
        }

        private void AssertMethodWasLocated(MethodInfo expected, Matcher matcher, params Type[] typesToBuildCompositeTypeFrom)
        {
            IList<MethodInfo> matches = new CompositeType(typesToBuildCompositeTypeFrom)
                                        .GetMatchingMethods(matcher, false);

            CollectionAssert.Contains(matches, expected);
        }

        private void AssertMethodWasLocated(string expectedMethodName, Matcher matcher, params Type[] typesToBuildCompositeTypeFrom)
        {
            IList<MethodInfo> matches = new CompositeType(typesToBuildCompositeTypeFrom)
                                        .GetMatchingMethods(matcher, false);

            foreach(MethodInfo method in matches)
            {
                if(method.Name == expectedMethodName)
                    return;
            }

            Assert.Fail("Could not match method '" + expectedMethodName + "'");
        }

        private void AssertAllMethodsWereLocated(MethodInfo[] expected, Matcher matcher, params Type[] typesToBuildCompositeTypeFrom)
        {
            IList<MethodInfo> matches = new CompositeType(typesToBuildCompositeTypeFrom)
                                        .GetMatchingMethods(matcher, false);

            CollectionAssert.IsSubsetOf(expected, matches);
        }

        #endregion
    }
}
